/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.geometr;

import cz.hamacekj.geometr.plugin.ConstructionObject;
import cz.hamacekj.geometr.plugin.ConstructionPlugin;
import cz.hamacekj.geometr.plugin.PrimitiveObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Model seznamu kroků.
 * @author hamacekh
 */
public class StepsModel implements ListModel<Step>, Iterable<Step> {

    private ArrayList<Step> steps = new ArrayList<>();
    private List<ListDataListener> dataListeners = new ArrayList<>();
    
    /**
     * Vrátí počet kroků v seznamu
     * @return počet kroků.
     */
    @Override
    public int getSize() {
        return steps.size();
    }
    
    /**
     * Alias pro {@link #getSize() getSize}
     * @return počet kroků.
     */
    public int length(){
        return getSize();
    }

    /**
     * Smaže krok na indexu.
     * @param index index mazaného kroku.
     */
    public void deleteElementAt(int index){
        steps.remove(index);
        ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index);
        fireListDataEvent(e);
    }
    
    /**
     * Vrátí krok na indexu.
     * @param index index elementu
     * @return krok
     */
    @Override
    public Step getElementAt(int index) {
        return steps.get(index);
    }

    @Override
    public void addListDataListener(ListDataListener l) {
        this.dataListeners.add(l);
    }

    @Override
    public void removeListDataListener(ListDataListener l) {
        this.dataListeners.remove(l);
    }

    @Override
    public Iterator<Step> iterator() {
        return new Iterator<Step>() {
            int i;
            boolean canRemove;
            {
                i = 0;
            }

            @Override
            public boolean hasNext() {
                return i < steps.size();
            }

            @Override
            public Step next() {
                canRemove = true;
                return steps.get(i++);
            }

            @Override
            public void remove() {
                if (!canRemove){
                    throw new IllegalStateException();
                }
                steps.remove(i - 1);
                canRemove = false;
                fireListDataEvent(new ListDataEvent(
                        StepsModel.this, ListDataEvent.INTERVAL_REMOVED, i - 1, i - 1));
            }
        };
    }
    
    public void addStep(Step step){
        this.steps.add(step);
        fireListDataEvent(new ListDataEvent(
                this, ListDataEvent.INTERVAL_ADDED,
                steps.size() - 1, steps.size() - 1));
    }
    
    /**
     * Smaže všechny prvky modelu a nastaví je na contents.
     * @param contents 
     */
    public void setContents(Collection<Step> contents){
        int endIndex = this.steps.size() - 1;
        this.steps.clear();
        this.fireListDataEvent(new ListDataEvent(
                this, ListDataEvent.INTERVAL_REMOVED, 0, endIndex));
        this.steps.addAll(contents);
        this.fireListDataEvent(new ListDataEvent(
                this, ListDataEvent.INTERVAL_ADDED, 0, this.steps.size()-1));
    }
    
    /**
     * Vrátí první objekt se stejným jménem jako name na který narazí.
     * @param name Jméno hledaného objektu.
     * @return ConstructionObject daného jména nebo null pokud takový není.
     */
    public ConstructionObject getObjectByName(String name){
        for(Step s: this){
            ConstructionObject o = s.getObjectByName(name);
            if (o != null){
                return o;
            }
        }
        return null;
    }
    
    /**
     * Vrátí první objekt se stejným jménem jako name a s jedním z typů type na který narazí.
     * @param name Jméno hledaného objektu.
     * @param type Pole možných typů hledaného objektu.
     * @return nalezený objekt nebo null, pokud takový není.
     */
    public ConstructionObject getObjectByName(String name, String[] type){
        for(Step s: this){
            ConstructionObject o = s.getObjectByName(name, type);
            if (o != null){
                return o;
            }
        }
        return null;
    }
    
    /**
     * Vytvoří DOM element obsahující informace o celém postupu konstrukce.
     * @param doc kontext xml dokument k vytváření elementů.
     * @return DOM element.
     */
    public Element createDOMElement(Document doc){
        Element element = doc.createElement("steps");
        for(Step s : this){
            element.appendChild(s.createDOMElement(doc));
        }
        return element;
    }
    
    /**
     * Naplní stepsModel daty z elementu el.
     * @param el Element.
     * @param plugins Načtené pluginy. Bude se v nich hledat createdBy atd..
     */
    public void fillFromDOMElement(Element el, Map<String, ConstructionPlugin> plugins){
        this.clear();
        NodeList list = el.getChildNodes();
        for(int i = 0; i < list.getLength(); i++){
            if(list.item(i).getNodeType() != Node.ELEMENT_NODE ||
                    !"step".equals(list.item(i).getNodeName())){
                continue;
            }
            Step s = Step.createFromDOMElement((Element)list.item(i), plugins);
            this.addStep(s);
        }
    }
    
    /**
     * Smaže všechny kroky.
     */
    public void clear(){
        int endIndex = steps.size() - 1;
        steps.clear();
        ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED,
                0, endIndex);
        fireListDataEvent(e);
    }
    
    /**
     * Vrátí Iterable třídu, která může iterovat skrz všechny primitivní objekty v tomto stepsModelu.
     * @return Iterovatelná třída.
     */
    public Iterable<PrimitiveObject> getPrimitiveObjectsView(){
        return new Iterable<PrimitiveObject>() {

            @Override
            public Iterator<PrimitiveObject> iterator() {
                return new Iterator<PrimitiveObject>() {
                    private Iterator<Step> stepIterator;
                    private Iterator<ConstructionObject> objectIterator = null;
                    private Iterator<PrimitiveObject> primitiveObjectIterator = null;
                    {
                        stepIterator = steps.iterator();
                    }

                    @Override
                    public boolean hasNext() {
                        return stepIterator.hasNext() || objectIterator.hasNext() || primitiveObjectIterator.hasNext();
                    }

                    @Override
                    public PrimitiveObject next() {
                        if(primitiveObjectIterator != null && primitiveObjectIterator.hasNext()){
                            return primitiveObjectIterator.next();
                        }
                        if(objectIterator != null && objectIterator.hasNext()){
                            ConstructionObject o = objectIterator.next();
                            primitiveObjectIterator = o.getPrimitiveObjects().iterator();
                            return primitiveObjectIterator.next();
                        }
                        Step s = stepIterator.next();
                        objectIterator = Arrays.asList(s.getConstructionObjects()).iterator();
                        ConstructionObject o = objectIterator.next();
                        primitiveObjectIterator = o.getPrimitiveObjects().iterator();
                        return primitiveObjectIterator.next();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException(
                                "No sense to remove primitive objects through this collection");
                    }
                };
            }
        };
    }
    
    
    /**
     * Vrátí Iterable třídu, která může iterovat skrz všechny {@link ConstructionObject objekty} v tomto stepsModelu.
     * @return Iterovatelná třída.
     */
    public Iterable<ConstructionObject> getConstructionObjectView(){
        return new Iterable<ConstructionObject>() {

            @Override
            public Iterator<ConstructionObject> iterator() {
                return new Iterator<ConstructionObject>() {
                    private Iterator<Step> stepsIterator;
                    private Iterator<ConstructionObject> constructionObjectIterator = null;

                    {
                        stepsIterator = steps.iterator();
                    }
                    
                    @Override
                    public boolean hasNext() {
                        return stepsIterator.hasNext() || constructionObjectIterator.hasNext();
                    }

                    @Override
                    public ConstructionObject next() {
                        if(constructionObjectIterator != null && constructionObjectIterator.hasNext()){
                            return constructionObjectIterator.next();
                        }
                        Step s = stepsIterator.next();
                        constructionObjectIterator = Arrays.asList(s.getConstructionObjects()).iterator();
                        return constructionObjectIterator.next();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException(
                                "No sense to delete constructionObjects via this iterator");
                    }
                };
            }
        };
    }
    
    private void fireListDataEvent(ListDataEvent e){
        for(ListDataListener l: this.dataListeners){
            l.contentsChanged(e);
        }
    }
            
}
